home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / info-service / gopher / Unix / xgopher.1.3 / util.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-05-25  |  23.0 KB  |  1,125 lines

  1. /* util.c
  2.    Utility routines to maintain and process gopher data structures
  3.    and events. */
  4.  
  5.      /*---------------------------------------------------------------*/
  6.      /* Xgopher        version 1.3     08 April 1993                  */
  7.      /*                version 1.2     20 November 1992               */
  8.      /*                version 1.1     20 April 1992                  */
  9.      /*                version 1.0     04 March 1992                  */
  10.      /* X window system client for the University of Minnesota        */
  11.      /*                                Internet Gopher System.        */
  12.      /* Allan Tuchman, University of Illinois at Urbana-Champaign     */
  13.      /*                Computing and Communications Services Office   */
  14.      /* Copyright 1992, 1993 by                                       */
  15.      /*           the Board of Trustees of the University of Illinois */
  16.      /* Permission is granted to freely copy and redistribute this    */
  17.      /* software with the copyright notice intact.                    */
  18.      /*---------------------------------------------------------------*/
  19.  
  20. /* ***** really, this file needs to divide into 3:
  21.         itemPublic.c itemUtil.c itemClass.c
  22.      there are some global variables to consider, though.
  23.    ***** */
  24.  
  25. #include <stdio.h>
  26.  
  27. #include "osdep.h"
  28. #include "conf.h"
  29. #include "gopher.h"
  30. #include "globals.h"
  31. #include "gui.h"
  32. #include "item.h"
  33. #include "itemList.h"
  34. #include "dir.h"
  35. #include "dirList.h"
  36. #include "util.h"
  37. #include "status.h"
  38. #include "misc.h"
  39. #include "net.h"
  40. #include "appres.h"
  41. #include "jobs.h"
  42.  
  43.  
  44. #include <ctype.h>
  45. #include <errno.h>
  46.  
  47. #include "sc_text.h"
  48. #include "sc_dir.h"
  49. #include "sc_cso.h"
  50. #include "sc_index.h"
  51. #include "sc_telnet.h"
  52. #include "sc_tn3270.h"
  53. #include "sc_binary.h"
  54. #include "sc_image.h"
  55. #include "sc_sound.h"
  56. #include "sc_extend.h"
  57.  
  58. char    prefixUnknown [ PREFIX_LEN ];
  59.  
  60. scInfo  noSubclass = {
  61.                 "unknown gopher item type",
  62.                 prefixUnknown,
  63.                 &nullHostList,
  64.                 GI_access,
  65.         GI_copyToFile,
  66.         copyTypeNone,
  67.         GI_process,
  68.         GI_init,
  69.         GI_done,
  70.         GI_restart
  71.         };
  72.  
  73. #define UNKNOWN_TYPE(gi)    ((gi)->sc == &noSubclass)
  74.  
  75.  
  76.  
  77. typedef struct giSubclassStruct {
  78.         char    typeID;
  79.     scInfo    *subclass;
  80.     struct giSubclassStruct *next;
  81.    } giSubclass;
  82.  
  83. /* the order of the following subclasses is important.  All directory
  84.    types must come first, then the extended types (so they can 
  85.    override internal ones), then all other built-in types. */
  86.  
  87. static giSubclass    definedSubclass[] = {
  88.         {A_DIRECTORY,        &dirSubclass,    NULL},
  89.         {A_INDEX,        &indexSubclass,    NULL},
  90.         {A_EXTENDED,        &extendSubclass,NULL},
  91.         {A_FILE,        &textSubclass,    NULL},
  92.         {A_CSO,            &csoSubclass,    NULL},
  93.         {A_BINARY,        &binarySubclass,NULL},
  94.         {A_IMAGE,        &imageSubclass,    NULL},
  95.         {A_TELNET,        &telnetSubclass,NULL},
  96.         {A_TN3270,        &tn3270Subclass,NULL},
  97.         {A_SOUND,        &soundSubclass,    NULL},
  98.         /* '?' type will be Unknown */
  99.         {'?',            &noSubclass,    NULL},
  100.         {'\0',            &noSubclass,    NULL},
  101.    };
  102.  
  103.         /* the following can be implemented as extended types: 
  104.  
  105.         A_MAC_BINHEX, (mac binhex),
  106.         A_DOS_BINHEX, (dos binhex),
  107.         A_UNIX_UUENCODE, (uuencoded file)
  108.     */
  109.  
  110. static    giSubclass    *activeSubclass = NULL;
  111.  
  112.  
  113. /* ====================================================================== */
  114. /*                                                                        */
  115. /* publicly callable procedures                                           */
  116. /*                                                                        */
  117. /* ====================================================================== */
  118.  
  119. /* makeItem
  120.    Given the contents, allocate and build a new gopher item */
  121.  
  122. gopherItemP
  123. makeItem(t, titleString, selectString, host, port, plus)
  124. char    t;
  125. char    *titleString;
  126. char    *selectString;
  127. char    *host;
  128. int    port;
  129. BOOLEAN    plus;
  130. {
  131.     gopherItemP    gi = newItem();
  132.     giSubclass     *scID;
  133.  
  134.     gi->type = t;
  135.     strncpy(USER_STRING(gi), titleString, USER_STRING_LEN);
  136.     vStringSet(&(gi->selector), selectString);
  137.     strncpy(gi->host, host, HOST_STRING_LEN);
  138.     gi->port = port;
  139.     gi->plus = FALSE;
  140.  
  141.     /* determine subclass and set fields determined by those class
  142.        variables and class methods. */
  143.  
  144.     for (scID = activeSubclass;
  145.          scID->typeID != t && scID->typeID != A_UNKNOWN;
  146.          scID=scID->next) ;
  147.     gi->sc = scID->subclass;
  148.  
  149.     PREFIX(gi, gi->sc->typePrefix);
  150.  
  151.     gi->accessOk = gi->sc->checkAccess(gi);
  152.  
  153.     return gi;
  154. }
  155.  
  156.  
  157. /* initItemClasses
  158.    initialize each item class - gather its resources, restrictions,
  159.    and if appropriate, create its user interface resources. */
  160.  
  161. void
  162. initItemClasses()
  163. {
  164.     giSubclass    *scID;
  165.  
  166.     for (scID = definedSubclass; scID->typeID != '\0'; scID++) {
  167.         scID->subclass->initProc();
  168.     }
  169.  
  170. #ifdef DEBUG
  171.     fprintf (stderr, "Known item types are:\n");
  172.     for (scID = activeSubclass; scID != NULL; scID=scID->next) {
  173.         fprintf (stderr, "\t%c, %.5s, %s\n",
  174.             scID->typeID, scID->subclass->typePrefix,
  175.             scID->subclass->typeName);
  176.     }
  177. #endif    /* DEBUG */
  178.  
  179.     return;
  180. }
  181.  
  182.  
  183. /* doneItemClasses
  184.    terminate each item class - free its resources and user interface
  185.    resources as necessary. */
  186.  
  187. void
  188. doneItemClasses()
  189. {
  190.     giSubclass *scID;
  191.  
  192.     for (scID = activeSubclass; scID != NULL; scID=scID->next) {
  193.         scID->subclass->doneProc();
  194.     }
  195.  
  196.     killAllItemProcesses();
  197.  
  198.     return;
  199. }
  200.  
  201.  
  202. /* restartItemClasses
  203.    restart each item class - re-initialize its resources and user
  204.    interface components as necessar. */
  205.  
  206. void
  207. restartItemClasses()
  208. {
  209.     giSubclass *scID;
  210.  
  211.     for (scID = activeSubclass; scID != NULL; scID=scID->next) {
  212.         scID->subclass->restartProc();
  213.     }
  214.  
  215.     return;
  216. }
  217.  
  218.  
  219. /* processItem
  220.    check accessability, then process the selected item */
  221.  
  222. BOOLEAN
  223. processItem(gi)
  224. gopherItemP     gi;
  225. {
  226.     BOOLEAN     result=FALSE;
  227.  
  228.     if (! gi->accessOk) {
  229.         char message[MESSAGE_STRING_LEN];
  230.  
  231.         sprintf(message,
  232.             "Sorry, %s access is restricted in this session.",
  233.             gi->sc->typeName);
  234.         showError(message);
  235.         result = FALSE;
  236.     } else {
  237.         result = gi->sc->processItem(gi);;
  238.     }
  239.  
  240.     return result;
  241. }
  242.  
  243.  
  244. /* copyItemToFile
  245.    check accessability, then copy the selected item to a file */
  246.  
  247. BOOLEAN
  248. copyItemToFile(gi)
  249. gopherItemP     gi;
  250. {
  251.     BOOLEAN     result=FALSE;
  252.  
  253.     if (! gi->accessOk) {
  254.         char message[MESSAGE_STRING_LEN];
  255.  
  256.         sprintf(message,
  257.             "Sorry, %s access is restricted in this session.",
  258.             gi->sc->typeName);
  259.         showError(message);
  260.         result = FALSE;
  261.     } else {
  262.         result = gi->sc->copyProc(gi);;
  263.     }
  264.  
  265.     return result;
  266. }
  267.  
  268.  
  269. /* updateDirectory
  270.    reload an existing gopher directory (possibly an index search result) */
  271.  
  272. void
  273. updateDirectory(d)
  274. gopherDirP    d;
  275. {
  276.     int        s;
  277.     gopherItemP    gi = d->selectorItem;
  278.     BOOLEAN        fetchOK;
  279.  
  280.  
  281.     if ( (s = GI_connectWithStatus(gi)) < 0) return;
  282.  
  283.     writeString(s, vStringValue(&(gi->selector)));
  284.     writeString(s, EOL_STRING);
  285.  
  286.     showStatus("Reloading requested directory", STAT_DIRECTORY,
  287.              gi->host, gi->port);
  288.  
  289.     fetchOK = GI_getGopherDir(s, d);
  290.  
  291.     close(s);
  292.  
  293.     if (! removeStatusPanel() ) {
  294.         
  295.         /* someone cancelled as the directory load finished */
  296.  
  297.         return;
  298.  
  299.     }
  300.  
  301.     if (! fetchOK) {
  302.         /* failure to re-load the directory */
  303.         showError(
  304.             "There seems to be no information in this directory\n");
  305.         return;
  306.     }
  307.  
  308.     setDirTime(d);
  309.  
  310.     return;
  311. }
  312.  
  313.  
  314. /* ====================================================================== */
  315. /*                                                                        */
  316. /* Utilities used by the item class and its subclasses                    */
  317. /*                                                                        */
  318. /* ====================================================================== */
  319.  
  320.  
  321. /* GU_ftpCheck
  322.    return True (ok) if ftp is allowed or this item doesn't represent
  323.    an ftp request.  Return false if this this is an ftp request and 
  324.    ftp's are not allowed. */
  325.  
  326. static BOOLEAN
  327. GU_ftpCheck(gi)
  328. gopherItemP    gi;
  329. {
  330.     BOOLEAN    isFtp = FALSE;
  331.  
  332.     if ( appResources->allowFtp ) return TRUE;
  333.  
  334.     isFtp = (strncmp(vStringValue(&(gi->selector)), "ftp:", 4) == 0);
  335.  
  336.     /* don't see an "ftp:", but see if there's an '@' in the path */
  337.     if (! isFtp) {
  338.         isFtp = (index(vStringValue(&(gi->selector)), '@') != NULL);
  339.     }
  340.  
  341.     return ( ! isFtp );
  342. }
  343.  
  344.  
  345. /* GU_makePrefix
  346.    edit application resources of directory listing prefixes to conform
  347.    to be blank padded and exactly PREFIX_LEN characters.
  348.    NOTE: These strings are *NOT* NULL-terminated. */
  349.  
  350. void
  351. GU_makePrefix(target, source)
  352. char    *target, *source;
  353. {
  354.     int    i;
  355.  
  356.     for (i=0; i<PREFIX_LEN; i++) {
  357.         if (*source == NULLC) {
  358.             *(target++) = ' ';
  359.         } else {
  360.             *(target++) = *(source++);
  361.         }
  362.     }
  363. }
  364.  
  365.  
  366. /* GU_getGopherItem
  367.    receive a response from a gopher server, parsing it as it comes in.
  368.    Return the item pointer for success, NULL for failure. */
  369.  
  370. static gopherItemP
  371. GU_getGopherItem(gfd)
  372. int    gfd;
  373. {
  374.     char            buffer[255];
  375.     char    gType,
  376.         gName[USER_STRING_LEN],
  377.         gPath[SELECTOR_STRING_MAX_LEN],
  378.         gHost[HOST_STRING_LEN];
  379.     int    gPort;
  380.     BOOLEAN    gPlus;
  381.  
  382.  
  383.     if (readn(gfd, &(buffer[0]), 1) <= 0) {
  384.         /* immediate EOF or error -- no item read */
  385.         return NULL;
  386.     }
  387.  
  388.     /** Get the kind of file from the first character **/
  389.  
  390.     gType = buffer[0];
  391.  
  392.     if (gType == A_EOI) return NULL;
  393.  
  394.     if (readField(gfd, &(buffer[1]), 255) <= 0) {
  395.         /* immediate EOF or error -- no item read */
  396.         return NULL;
  397.     }
  398.  
  399.     /* get the User Displayable name */
  400.  
  401.     strncpy(gName, buffer + 1, USER_STRING_LEN);
  402.  
  403.     /* terminate the string just in case it was too long */
  404.  
  405.     gName[USER_STRING_LEN - 1] = '\0';
  406.  
  407.     /* get the Pathname */
  408.  
  409.     if (readField(gfd, gPath, SELECTOR_STRING_MAX_LEN) <= 0) {
  410.         /* EOF or error after TYPE/NAME  --
  411.            no complete item read */
  412.         return NULL;
  413.     }
  414.  
  415.     /* get the hostname */
  416.  
  417.     if (readField(gfd, gHost, HOST_STRING_LEN) == 0) {
  418.         /* EOF or error after TYPE/NAME/PATH  --
  419.            no complete item read */
  420.         return NULL;
  421.     }
  422.  
  423.     /* get the port number */
  424.  
  425.     if (readLine(gfd, buffer, 255)<=0) {
  426.         /* EOF or error after TYPE/NAME/PATH  --
  427.            Problem - should be a port number here.  But this
  428.            isn't as serious as above.  We'll try to proceed. */
  429.         ;
  430.     }
  431.  
  432.     gPort = 0;
  433.  
  434.     /* Get the port number */
  435.  
  436.     gPort = atoi(buffer);
  437.  
  438.     gPlus = False;
  439.  
  440.     return makeItem(gType, gName, gPath, gHost, gPort, gPlus);
  441. }
  442.  
  443.  
  444. /* GU_copySubclassRecord
  445.    return a copy of the class record for subclass identified.
  446.    Search both knownTypes, and types already initialized.  */
  447.  
  448. scInfo    *
  449. GU_copySubclassRecord(t)
  450. char    t;
  451. {
  452.     scInfo        *scRec;
  453.     giSubclass     *scID;
  454.  
  455.     scRec = (scInfo *) malloc(sizeof(scInfo));
  456.  
  457.     /* first check initialized types.  This allows an alias
  458.        to preferentially use a new external type. */
  459.  
  460.     for (scID = activeSubclass;
  461.             scID != NULL && scID->typeID != t; scID=scID->next) ;
  462.  
  463.     if (scID == NULL) {
  464.         /* it will be in this list or the final item type, unknown
  465.            will be used */
  466.         for (scID = definedSubclass;
  467.              scID->typeID != t && scID->typeID != '\0'; scID++) ;
  468.     }
  469.  
  470.     bcopy((char *) scID->subclass, (char *) scRec, sizeof(scInfo));
  471.  
  472.     return scRec;
  473. }
  474.  
  475.  
  476. /* GU_registerNewType
  477.    Add a new type identifier and its subclass record to the known list. */
  478.  
  479. void
  480. GU_registerNewType(t, scRec)
  481. char    t;
  482. scInfo    *scRec;
  483. {
  484.     giSubclass    *newSC = (giSubclass *) malloc (sizeof (giSubclass));
  485.     giSubclass    *sc;
  486.  
  487.     newSC->typeID   = t;
  488.     newSC->subclass = scRec;
  489.     newSC->next     = (giSubclass *) NULL;
  490.  
  491.     /* add to the end of the list for expected behavior */
  492.  
  493.     if (activeSubclass == (giSubclass *) NULL) {
  494.         activeSubclass = newSC;
  495.     } else {
  496.         for (sc=activeSubclass; sc != NULL; sc=sc->next) {
  497.         if (sc->typeID == t) {    /* already is one */
  498.             break;
  499.         }
  500.  
  501.         if (sc->next == (giSubclass *) NULL) {
  502.             sc->next = newSC;
  503.             break;
  504.         }
  505.         }
  506.     }
  507. }
  508.  
  509.  
  510. /* GU_isVowel
  511.    return True if first character of a string is a vowel, false otherwise */
  512.  
  513. static    char    vowels[] = "aAeEiIoOuU";
  514.  
  515. BOOLEAN
  516. GU_isVowel(str)
  517. char    *str;
  518. {
  519.     char    *v;    
  520.     for (v=vowels; (*str != *v  &&  *v != NULLC); v++ );
  521.  
  522.     return (*v != NULLC);
  523. }
  524.  
  525. /* Ideas for the implementation of the access limiting mechanism
  526.    came from the University of Minnesota gopher server code.
  527.    In particular, the matching of IP addresses and host name
  528.    matching algorithm is from that code. */
  529.  
  530.  
  531. /* GU_addHostToAccessList
  532.    add the host name or address to an access list.
  533.    Determine if a given string is a name or IP address by
  534.    checking the first character to see if it is numeric. */
  535.  
  536. static void
  537. GU_addHostToAccessList(list, host)
  538. accessList    *list;
  539. char        *host;
  540. {
  541.     char        *temp;
  542.     accessList    addr;
  543.  
  544.     temp = (char *) malloc((strlen(host) + 1) * sizeof(char));
  545.     strcpy(temp, host);
  546.     addr = (accessListItem *) malloc(sizeof(accessListItem));
  547.     addr->address = temp;
  548.     addr->numeric = isdigit(*host) ? TRUE : FALSE;
  549.  
  550.     /* For matching, it should not matter whether the list is
  551.        created in the same order as specified or reverse order,
  552.        so each item in the input list will be added to the front. */
  553.  
  554.     addr->next = *list;
  555.  
  556.     *list       =  addr;
  557.  
  558.     return;
  559. }
  560.  
  561.  
  562. /* GU_createAccessList
  563.    create an access list from a string of hostname/IP addresses */
  564.  
  565. accessList
  566. GU_createAccessList(string)
  567. char    *string;
  568. {
  569.     /* pull out host or domain names separated by white space */
  570.  
  571.     char        *cp;
  572.     char        name[256];
  573.     char        *np;
  574.     accessList    list = (accessList) NULL;
  575.  
  576.     cp = string;
  577.     while (*cp != '\0') {
  578.         while (isspace(*cp)  &&  *cp != '\0') { cp++; };
  579.         if ( *cp == '\0' ) break;
  580.  
  581.         np = &(name[0]);
  582.         while (! isspace(*cp)  &&  *cp != '\0') {
  583.             *np = *cp;
  584.             np++;
  585.             cp++;
  586.         }
  587.         *np='\0';
  588.  
  589.         GU_addHostToAccessList(&list, name);
  590.     }
  591.  
  592.     return list;
  593. }
  594.  
  595.  
  596. /* GU_checkAccess
  597.    see if a proposed host access is allowed */
  598.  
  599. BOOLEAN
  600. GU_checkAccess(host, permitList)
  601. char        *host;
  602. accessList    permitList;
  603. {
  604.     accessListItem    *allowed;
  605.     int    hostLen, addrLen;
  606.  
  607.     if (permitList == NULL) return TRUE;
  608.  
  609.     for (allowed=permitList; allowed!=NULL; allowed=allowed->next) {
  610.         hostLen = strlen(host); 
  611.         addrLen = strlen(allowed->address); 
  612.         if (allowed->numeric) {
  613.  
  614.             /* numeric IP address -- compare from start */
  615.  
  616.             if (addrLen <= hostLen) {
  617.             if (strncmp(host, allowed->address, addrLen) == 0) {
  618.                 return TRUE;
  619.             }
  620.  
  621.             }
  622.         } else {
  623.  
  624.             /* character host/domain name -- compare from end */
  625.  
  626.             if (addrLen <= hostLen) {
  627.             if (strcasecmp((host+hostLen-addrLen),
  628.                     allowed->address) == 0) {
  629.                 return TRUE;
  630.             }
  631.             }
  632.         }
  633.     }
  634.  
  635.     return FALSE;
  636. }
  637.  
  638.  
  639. #ifdef DEBUG
  640. void
  641. GU_printAccessList(list)
  642. accessList    list;
  643. {
  644.     accessListItem    *l;
  645.  
  646.     fprintf (stdout, "Host Access List:\n");
  647.  
  648.     for (l=list; l != (accessList) NULL; l=l->next) {
  649.         fprintf (stdout, "\t%s %s\n", l->address,
  650.                 l->numeric ? "(IP address)" : "(host name)");
  651.     }
  652.  
  653.     fprintf (stdout, "    End of Host Access List\n");
  654. }
  655. #endif /* DEBUG */
  656.  
  657.  
  658. /* ====================================================================== */
  659. /*                                                                        */
  660. /* Class methods and common subclass methods of gopher item               */
  661. /*                                                                        */
  662. /* ====================================================================== */
  663.  
  664.  
  665. /* GI_connnectWithStatus
  666.    make a socket connection, posting the status popup and leaving it
  667.    posted for the following processing */
  668.  
  669. int
  670. GI_connectWithStatus(gi)
  671. gopherItemP    gi;
  672. {
  673.     int    s;
  674.  
  675.     showStatus("Connecting to Gopher", STAT_CONNECT,
  676.             gi->host, gi->port);
  677.     s = connectToSocket(gi->host, gi->port);
  678.     if (s < 0) {
  679.  
  680.         (void) removeStatusPanel();
  681.  
  682.         /* could not make a network connection to receive file */
  683.  
  684.         networkError(s, gi->host, gi->port);
  685.         return -1;
  686.     }
  687.  
  688.     /* check for cancel during connection */
  689.  
  690.     if (!updateStatusPanel(1, 0)) {
  691.         (void) removeStatusPanel();
  692.  
  693.         close(s);
  694.         return -1;
  695.     }
  696.  
  697.     return s;
  698. }
  699.  
  700.  
  701. /* GI_getGopherDir
  702.    receive a directory from a gopher server.
  703.    Return the directory pointer for success, NULL for failure. */
  704.  
  705. BOOLEAN
  706. GI_getGopherDir(gfd, d)
  707. int    gfd;
  708. gopherDirP    d;
  709. {
  710.     gopherItemP    gi;
  711.     char        buffer[512];
  712.     int        j, n=0;
  713.     BOOLEAN        goOn;
  714.  
  715.     /* get each item; for each one, decide if it should be kept
  716.        in the directory.  */
  717.  
  718.  
  719.     while ( (gi = GU_getGopherItem(gfd)) != NULL ) {
  720.  
  721.         n++;
  722.  
  723.         if (appResources->showItems == showAll) {
  724.             appendItem(&(d->contents), gi);
  725.         }
  726.             
  727.         else if (appResources->showItems == showKnown  &&
  728.             ! UNKNOWN_TYPE(gi)) {
  729.                 appendItem(&(d->contents), gi);
  730.         }
  731.  
  732.         else if (appResources->showItems == showAccessible  &&
  733.             gi->accessOk) {
  734.                 appendItem(&(d->contents), gi);
  735.         }
  736.  
  737.         else if (appResources->showItems == showAvailable  &&
  738.             ! UNKNOWN_TYPE(gi)  &&  gi->accessOk) {
  739.                 appendItem(&(d->contents), gi);
  740.         }
  741.  
  742.         else {
  743.             freeItem(gi);
  744.             n--;
  745.         } 
  746.  
  747.         if (n % 5 == 0) {
  748.             goOn = updateStatusPanel(n, 0);
  749.             if (!goOn) {
  750.                 freeItemList(&(d->contents));
  751.                 break;
  752.             }
  753.         }
  754.     }
  755.  
  756.     return  (itemListLength(&(d->contents)) != 0);
  757.  
  758.  
  759. /* GI_copyFromNet
  760.    Generic copy setup.  Makes network connection, updates status
  761.    and invokes a type-specific copy procedure. */
  762.  
  763. BOOLEAN
  764. GI_copyFromNet(outFD, gi, specialMsg)
  765. int        outFD;
  766. gopherItemP    gi;
  767. char        *specialMsg;
  768. {
  769.     int    s;
  770.     char    message[MESSAGE_STRING_LEN];
  771.     BOOLEAN rc, goOn = TRUE;
  772.  
  773.     if (gi->sc->copyDataType == copyTypeNone) return;
  774.  
  775.     if (specialMsg == (char *) NULL) {
  776.         sprintf(message, "Copying the %s", gi->sc->typeName);
  777.     } else {
  778.         strcpy(message, specialMsg);
  779.     }
  780.  
  781.     if ((s = GI_connectWithStatus(gi)) < 0) return FALSE;
  782.  
  783.     writeString(s, vStringValue(&(gi->selector)));
  784.     writeString(s, EOL_STRING);
  785.  
  786.     switch (gi->sc->copyDataType) {
  787.  
  788.         case copyTypeAscii:
  789.         rc = GI_copyAsciiFromNet(s, outFD, gi, message);
  790.         break;
  791.     
  792.         case copyTypeBinaryEOF:
  793.         rc = GI_copyBinaryFromNetEOF(s, outFD, gi, message);
  794.         break;
  795.  
  796.         default:
  797.         rc = FALSE;
  798.         break;
  799.     }
  800.  
  801.     close(s);
  802.  
  803.     /* check for cancel */
  804.  
  805.     goOn = removeStatusPanel();
  806.  
  807.     return (rc && goOn);
  808. }
  809.  
  810.  
  811.  
  812. #define MIN_OF(a,b)    ((a)<(b) ? (a) : (b))
  813. #define BIN_BUFFER_SIZE    1400
  814.  
  815. /* GI_copyBinaryFromNetEOF
  816.    Binary copy the contents of one open file descriptor to another.
  817.    Copy until EOF
  818.  
  819.    The file descriptor references on open file for the result.
  820.    For input, we will assume blocking I/O, so a read will always
  821.    return something if it's not yet eof. */
  822.  
  823. BOOLEAN
  824. GI_copyBinaryFromNetEOF(s, outFD, gi, msg)
  825. int        s, outFD;
  826. gopherItemP    gi;
  827. char        *msg;
  828. {
  829.     char    buf[BIN_BUFFER_SIZE];
  830.     int    nr, nw;
  831.     int    byteCt=0;
  832.     BOOLEAN goOn = TRUE;
  833.  
  834.     showStatus(msg, STAT_BINARY,
  835.                 gi->host, gi->port);
  836.  
  837.     while (TRUE) {
  838.         nr = read(s, buf, BIN_BUFFER_SIZE);
  839.  
  840.         if (nr <= 0) {
  841.             if (nr == -1) {
  842.                 perror("GI_copyBinaryFromNetEOF");
  843.                 fprintf (stderr,
  844.                 "Error copying binary data - input.\n");
  845.             }
  846.             break;
  847.         }
  848.  
  849.         byteCt += nr;
  850.         goOn = updateStatusPanel(byteCt, 0);
  851.         if (!goOn) {
  852.             break;
  853.         }
  854.  
  855.         nw = write(outFD, buf, nr);
  856.         if (nw != nr  ||  nw < 0) {
  857.             perror("GI_copyBinaryFromNetEOF");
  858.             fprintf (stderr,
  859.                 "Error copying binary data - output.\n");
  860.             break;
  861.         }
  862.     }
  863.  
  864.     return goOn;
  865. }
  866.  
  867.  
  868. /* GI_copyBinaryFromNetLen
  869.    Binary copy the contents of one open file descriptor to another.
  870.    Copy until a specific number of characters has been copied.
  871.  
  872.    The file descriptor references on open file for the result.
  873.    For input, we will assume blocking I/O, so a read will always
  874.    return something if it's not yet eof. */
  875.  
  876. BOOLEAN
  877. GI_copyBinaryFromNetLen(s, outFD, gi, msg)
  878. int        s, outFD;
  879. gopherItemP    gi;
  880. char        *msg;
  881. {
  882.     char    buf[BIN_BUFFER_SIZE];
  883.     int    nr, nw;
  884.     int    byteCt=0;
  885.     BOOLEAN goOn = TRUE;
  886.     int    nBytes = 0;
  887.  
  888.  
  889.     showStatus(msg, STAT_BINARY,
  890.                 gi->host, gi->port);
  891.  
  892.     while (nBytes > 0) {
  893.         nr = read(s, buf, MIN_OF(nBytes, BIN_BUFFER_SIZE));
  894.  
  895.         if (nr <= 0) {
  896.             if (nr == -1) {
  897.                 perror("GI_copyBinaryFromNetLen");
  898.                 fprintf (stderr,
  899.                 "Error copying binary data - input.\n");
  900.             }
  901.             break;
  902.         }
  903.  
  904.         nBytes -= nr;
  905.  
  906.         byteCt += nr;
  907.         goOn = updateStatusPanel(byteCt, 0);
  908.         if (!goOn) {
  909.             break;
  910.         }
  911.  
  912.         nw = write(outFD, buf, nr);
  913.         if (nw != nr  ||  nw < 0) {
  914.             perror("GI_copyBinaryFromNetLen");
  915.             fprintf (stderr,
  916.                 "Error copying binary data - output.\n");
  917.             break;
  918.         }
  919.     }
  920.  
  921.     return goOn;
  922. }
  923.  
  924.  
  925. /* GI_copyAsciiFromNet
  926.    Copy the contents of one open text file descriptor to another.  Copy
  927.    until a line with a single period in column one or an end of file.
  928.  
  929.    Change "network ASCII" with CR-LF ending each line to standard
  930.    Unix text files with a NL at the end of each line.
  931.  
  932.    Either file descriptor may reference a file or a socket connection. 
  933.    For sockets, we will assume blocking I/O, so a read will always
  934.    return something if it's not yet eof, and a write will write everything. */
  935.  
  936. static char    newLine[1] = {NL};
  937.  
  938. BOOLEAN
  939. GI_copyAsciiFromNet(s, outFD, gi, msg)
  940. int        s, outFD;
  941. gopherItemP    gi;
  942. char        *msg;
  943. {
  944.     char    line[FILE_LINE_LEN];
  945.     int    len;
  946.     int    lineCt=0, wordCt=0;
  947.     BOOLEAN    goOn = TRUE;
  948.  
  949.  
  950.     showStatus(msg, STAT_ASCII,
  951.                 gi->host, gi->port);
  952.  
  953.     if (readLine(s, line, FILE_LINE_LEN) <= 0) {
  954.         (void) removeStatusPanel();
  955.         return FALSE;
  956.     }
  957.     zapCRLF(line);
  958.  
  959.  
  960.     while ( line[0] != '.'  ||  line[1] != '\0') {
  961.  
  962.         lineCt++;
  963.         if ( (lineCt < 50   && lineCt % 10 == 0)  ||
  964.              (lineCt < 500  && lineCt % 50 == 0)  ||
  965.               lineCt % 100 == 0 ) {
  966.             goOn = updateStatusPanel(lineCt, 0);
  967.             if (!goOn) {
  968.                 break;
  969.             }
  970.         }
  971.  
  972.         len = strlen(line);
  973.         write(outFD, line, len);
  974.         write(outFD, newLine, 1);
  975.  
  976.         if (readLine(s, line, FILE_LINE_LEN) <= 0) break;
  977.         zapCRLF(line);
  978.     }
  979.  
  980.     return goOn;
  981. }
  982.  
  983.  
  984.  
  985. #define NET_BUFFER_SIZE    1400
  986.  
  987. /* GI_copyNetUntilEOF
  988.    copy chunks of data from a network file descriptor to a file pointer.
  989.    No termination condition is expected except EOF on the input fd.
  990.    This is a blocking read. */
  991.  
  992. void
  993. GI_copyNetUntilEOF(s, fp)
  994. int    s;
  995. FILE    *fp;
  996. {
  997.     char    buf[NET_BUFFER_SIZE];
  998.     int    j;
  999.  
  1000.     while (TRUE) {
  1001.         j = read(s, buf, NET_BUFFER_SIZE);
  1002.  
  1003.  
  1004.         if (j <= 0) {
  1005.             if (j == -1)
  1006.                 fprintf (stderr,
  1007.                 "Error (%d) copying data from the network\n",
  1008.                     errno);
  1009.             break;
  1010.         }
  1011.         
  1012.         if (fwrite(buf, 1, j, fp) != j) {
  1013.             return;
  1014.         }
  1015.     }
  1016. }
  1017.  
  1018.  
  1019. /* GI_noCopyItem
  1020.    default "can't copy" procedure */
  1021.  
  1022. BOOLEAN
  1023. GI_noCopyItem(gi)
  1024. gopherItemP    gi;
  1025. {
  1026.     int        s;
  1027.     char        *lastName;
  1028.     char        message[MESSAGE_STRING_LEN];
  1029.     
  1030.     sprintf(message, "You cannot copy %s %s to a file.",
  1031.         (GU_isVowel(gi->sc->typeName) ? "an" : "a"), gi->sc->typeName);
  1032.     showError(message);
  1033.  
  1034.     return FALSE;
  1035. }
  1036.  
  1037.  
  1038. /* GI_copyToFile
  1039.    copy a gopher item directly to a file */
  1040.  
  1041. BOOLEAN
  1042. GI_copyToFile(gi)
  1043. gopherItemP    gi;
  1044. {
  1045.  
  1046.     saveNetRequest(gi);
  1047.     return TRUE;
  1048. }
  1049.  
  1050.  
  1051. /* GI_access
  1052.    check the access limits for an item.  This class method will be
  1053.    overridden for many subclasses which need to check the application
  1054.    resources for permission.  */
  1055.  
  1056. BOOLEAN
  1057. GI_access(gi)
  1058. gopherItemP    gi;
  1059. {
  1060.     BOOLEAN    result;
  1061.  
  1062.     result = GU_ftpCheck(gi);
  1063.  
  1064.     return result;
  1065. }
  1066.  
  1067.  
  1068. /* GI_init
  1069.    default class  initialization procedure a subclass of Gopher Item.  This
  1070.    procedure works for class "unknown". */
  1071.  
  1072. void
  1073. GI_init()
  1074. {
  1075.     GU_makePrefix(prefixUnknown,  appResources->prefixUnknown);
  1076.     GU_registerNewType(A_UNKNOWN, &noSubclass);
  1077.  
  1078.     return;
  1079. }
  1080.  
  1081.  
  1082. /* GI_done
  1083.    default class termination procedure a subclass of Gopher Item.  This
  1084.    procedure is a no-op. */
  1085.  
  1086. void
  1087. GI_done()
  1088. {
  1089.     return;
  1090. }
  1091.  
  1092.  
  1093. /* GI_restart
  1094.    default restart cleanup for a subclass of Gopher Item.  This
  1095.    procedure is a no-op. */
  1096.  
  1097. void
  1098. GI_restart()
  1099. {
  1100.     return;
  1101. }
  1102.  
  1103.  
  1104. /* GI_process
  1105.    default processing procedure for a subclass of Gopher Item.  This
  1106.    procedure is a no-op, suggesting an alternative means of processing
  1107.    the item.  Every known item type should override this procedure. */
  1108.  
  1109. BOOLEAN
  1110. GI_process(gi)
  1111. gopherItemP     gi;
  1112. {
  1113.     char message[MESSAGE_STRING_LEN];
  1114.  
  1115.     sprintf(message,
  1116.         "There is no automatic processing for this gopher item type (%c)",
  1117.         gi->type);
  1118.     strcat(message,
  1119.         "\nYou may be able to use the Copy command to access the file.");
  1120.     showError(message);
  1121.  
  1122.     return FALSE;
  1123. }
  1124.